package cn.jinbyte.web.interceptor;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.jinbyte.web.annotation.ApiLog;
import cn.jinbyte.web.config.ApiLogProperties;
import cn.jinbyte.web.dao.ApiLogDao;
import cn.jinbyte.web.model.ApiLogType;
import cn.jinbyte.web.model.LogInclude;
import cn.jinbyte.web.model.LogRecord;
import cn.jinbyte.web.utils.helper.WebLogHelper;
import com.google.common.collect.Sets;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;

import java.time.Duration;
import java.time.Instant;
import java.util.Set;

import static cn.jinbyte.web.config.WebConst.ATTR_START_LOG_TIME;

/**
 * 接口日志处理类
 *
 * @author jinty
 */
@Slf4j
public class ApiLogInterceptor extends RequestLogInterceptor {
    private final ApiLogProperties properties;

    public ApiLogInterceptor(ApiLogProperties properties, ApiLogDao logDao) {
        super(properties.getIncludes(), logDao);
        this.properties = properties;
    }

    @Override
    public boolean isLog(HandlerMethod handlerMethod) {
        if (!properties.isEnabled()) {
            return false;
        }
        ApiLog apiLog = getApiLog(handlerMethod);
        return apiLog != null && !apiLog.ignore();
    }

    /**
     * 获取接口访问日志注解
     *
     * @param handlerMethod 目标方法
     * @return 接口访问日志注解
     */
    private ApiLog getApiLog(HandlerMethod handlerMethod) {
        ApiLog methodLog = AnnotationUtil.getAnnotation(handlerMethod.getMethod(), ApiLog.class);
        if (methodLog != null) {
            return methodLog;
        }
        // 如果接口类上有 @ApiLog 注解，且要求忽略该接口，则不记录日志
        return AnnotationUtil.getAnnotation(handlerMethod.getBeanType(), ApiLog.class);
    }

    /**
     * 获取可选包含信息
     *
     * @param includes 包含信息
     * @param apiLog   接口访问日志注解
     */
    private Set<LogInclude> getOptionalIncludes(Set<LogInclude> includes, ApiLog apiLog) {
        Set<LogInclude> logIncludes = Sets.newHashSet(includes);
        LogInclude[] includeArr = apiLog.includes();
        if (includeArr.length > 0) {
            logIncludes.addAll(Set.of(includeArr));
        }
        LogInclude[] excludeArr = apiLog.excludes();
        if (excludeArr.length > 0) {
            logIncludes.removeAll(Set.of(excludeArr));
        }
        return logIncludes;
    }

    @Override
    public LogRecord buildLog(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod,
                              Exception ex, Set<LogInclude> includes) {
        ApiLog apiLog = getApiLog(handlerMethod);
        if (apiLog == null || apiLog.ignore()) {
            return null;
        }

        if (apiLog.value() == ApiLogType.ERROR && ex == null) {
            return null;
        }

        Instant startLogTime = (Instant) request.getAttribute(ATTR_START_LOG_TIME);

        Set<LogInclude> logIncludes = getOptionalIncludes(includes, apiLog);

        return LogRecord.builder().timestamp(startLogTime)
                .module(WebLogHelper.logModule(handlerMethod, apiLog))
                .domain(apiLog.domain())
                .description(apiLog.description())
                .accessUser(WebLogHelper.logAccessUser(request))
                .request(WebLogHelper.logRequest(request, logIncludes))
                .response(WebLogHelper.logResponse(response, logIncludes))
                .errorMsg(ex == null ? null : ex.getMessage())
                .timeTaken(Duration.between(startLogTime, Instant.now()))
                .build();
    }

    @Override
    public void log(LogRecord logRecord) {
        log.info("API日志：{}，方法：{}， IP：{}， 耗时：{}ms", logRecord.request().uri(), logRecord.request().method(),
                logRecord.request().ip(), logRecord.timeTaken().toMillis());
        super.log(logRecord);
    }
}
